use std::str;

// dfs path
#[derive(Debug, Clone)]
pub(crate) struct DfsPath {
    inner: Vec<u8>,
}

impl DfsPath {
    // 构建一个新的路径
    pub(crate) fn new(s: &str) -> Self {
        Self { inner: Vec::from(s) }
    }

    // 路径是否为空
    #[inline]
    pub(crate) fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    // 路径名称长度
    #[inline]
    pub(crate) fn len(&self) -> usize {
        self.inner.len()
    }

    // 路径指定位置 char
    #[inline]
    fn to_char(&self, index: usize) -> char {
        char::from(self.inner[index])
    }

    // 路径是绝对路径
    pub(crate) fn is_absolute(&self) -> bool {
        if self.is_empty() {
            return false;
        }
        let c = self.to_char(0);
        c == '/'
    }

    // 按照 "/" 层次划分每个片段
    pub(crate) fn to_slice(&self) -> Vec<String> {
        let mut v = vec![];
        let mut start = 0;
        if self.is_absolute() {
            start = 1;
        }
        let slice = &self.inner[start..];
        let mut start = 0;
        let mut end = 0;
        for i in slice.iter() {
            if *i == b'/' {
                let new = &slice[start..end];
                let p = String::from_utf8(new.to_vec()).ok().unwrap();
                if !p.is_empty() {
                    v.push(p);
                }
                start = end + 1;
            }
            end += 1;
        }
        if start < end {
            let p = String::from_utf8(slice[start..end].to_vec()).ok().unwrap();
            if !p.is_empty() {
                v.push(p);
            }
        }
        v
    }
}

#[cfg(feature = "command")]
impl DfsPath {
    // 路径转 str
    #[inline]
    pub(crate) fn to_str(&self) -> Option<&str> {
        str::from_utf8(self.inner.as_slice()).ok()
    }

    fn new_from_slice(s: &[u8]) -> Self {
        Self { inner: Vec::from(s) }
    }

    // 当前路径的父路径
    pub(crate) fn parent(&self) -> Option<DfsPath> {
        if self.is_empty() {
            return None;
        }
        // 如果已经是根路径, 那么返回根路径
        if self.len() == 1 && self.is_absolute() {
            return Some(self.clone());
        }

        let mut index = self.len() - 1;

        let c = self.to_char(index);
        if c == '/' {
            index -= 1;
        }

        for mut i in (0..index + 1).rev() {
            if self.to_char(i) == '/' {
                if i == 0 {
                    i += 1;
                }
                let new = Self::new_from_slice(&self.inner[0..i]);
                return Some(new);
            }
        }

        None
    }

    // 路径对应的文件名
    pub(crate) fn file_name(&self) -> Option<String> {
        let end = self.len() - 1;
        if self.to_char(end) == '/' {
            return None;
        }

        for mut i in (0..end + 1).rev() {
            if self.to_char(i) == '/' {
                i += 1;
                let slice = &self.inner[i..end + 1];
                let v = slice.to_vec();
                let new = String::from_utf8(v).ok().unwrap();
                return Some(new);
            }
        }
        let slice = &self.inner[0..end + 1];
        let v = slice.to_vec();
        Some(String::from_utf8(v).ok().unwrap())
    }
}

#[cfg(all(test, feature = "command"))]
mod tests {
    use super::*;
    #[test]
    fn path_test() {
        let path = DfsPath::new("aa");
        let parent = path.parent();
        assert!(parent.is_none());
        let file = path.file_name().unwrap();
        assert_eq!(file, "aa");

        let path = DfsPath::new("/aa");
        assert_eq!(path.to_str().unwrap(), "/aa");

        let path = DfsPath::new("/aa/");
        assert_eq!(path.to_str().unwrap(), "/aa/");

        let path = DfsPath::new("/aa/");
        let parent = path.parent().unwrap();
        assert_eq!(parent.to_str().unwrap(), "/");

        let path = DfsPath::new("/aa/b");
        let parent = path.parent().unwrap();
        assert_eq!(parent.to_str().unwrap(), "/aa");

        let path = DfsPath::new("/aa/b/");
        let parent = path.parent().unwrap();
        assert_eq!(parent.to_str().unwrap(), "/aa");

        let path = DfsPath::new("/aa");
        let parent = path.parent().unwrap();
        assert_eq!(parent.to_str().unwrap(), "/");

        let path = DfsPath::new("/../.");
        let parent = path.parent().unwrap();
        assert_eq!(parent.to_str().unwrap(), "/..");

        let path = DfsPath::new("/.");
        let parent = path.parent().unwrap();
        assert_eq!(parent.to_str().unwrap(), "/");

        let path = DfsPath::new("/");
        let parent = path.parent().unwrap();
        assert_eq!(parent.to_str().unwrap(), "/");

        let path = DfsPath::new("/");
        assert!(path.is_absolute());

        let path = DfsPath::new("/.");
        assert!(path.is_absolute());

        let path = DfsPath::new("./.");
        assert!(!path.is_absolute());

        let path = DfsPath::new("/.");
        let file = path.file_name().unwrap();
        assert_eq!(file.as_str(), ".");

        let path = DfsPath::new("/.a");
        let file = path.file_name().unwrap();
        assert_eq!(file.as_str(), ".a");

        let path = DfsPath::new("/.a/b");
        let file = path.file_name().unwrap();
        assert_eq!(file.as_str(), "b");

        let path = DfsPath::new("/.a/b/");
        let file = path.file_name();
        assert!(file.is_none());

        let path = DfsPath::new("./.././aa/bb/./cc/");
        let slice = path.to_slice();
        assert_eq!(".".to_string(), slice[0]);
        assert_eq!("..".to_string(), slice[1]);
        assert_eq!(".".to_string(), slice[2]);
        assert_eq!("aa".to_string(), slice[3]);
        assert_eq!("bb".to_string(), slice[4]);
        assert_eq!(".".to_string(), slice[5]);
        assert_eq!("cc".to_string(), slice[6]);

        let path = DfsPath::new("/./../aa");
        let slice = path.to_slice();
        assert_eq!(".".to_string(), slice[0]);
        assert_eq!("aa".to_string(), slice[2]);

        let path = DfsPath::new("/.");
        let slice = path.to_slice();
        assert_eq!(".".to_string(), slice[0]);
    }
}
